package bplustree;

import java.io.IOException;
import java.util.LinkedList;
import java.util.ListIterator;

import register.Register;
import register.RegisterID;

public class Node {
	
	protected BPlusTree tree;
	protected int node_size; //bytes
	protected int free_space;
	protected int level;
	protected int position;
	protected int register_count;
	
	protected LinkedList<Integer> childrenList; //OJO, CAMBIO GRANDE EN PERSISTENCIA
	protected LinkedList<Register> registerList;
	
	
	
	
	public Node(int level, BPlusTree tree) {
		this.level=level;
		this.tree=tree;
		position=-1;
		register_count=0;
		node_size=tree.getNode_size();
		childrenList=new LinkedList<Integer>();
		registerList=new LinkedList<Register>();
	}

	/**
	 * persists the node
	 * CANNOT USE MORE THAN NODE_SIZE
	 * @throws IOException 
	 *
	 */
	
	public void writeChild(Node node) throws IOException{
		if(node.position<0) node.position=tree.getFree_positions();
			//System.out.println("Escribiendo en la posicion" + node.position);
		tree.getFile().seek(node.position*node.node_size);
			//System.out.println("Escribiendo a partir del byte " + tree.getFile().getFilePointer());
		//System.out.println(tree.getFile().getFilePointer());
		ListIterator<Register> iterReg=node.registerList.listIterator();
		ListIterator<Integer> iterChild=node.childrenList.listIterator();
		
		while(iterChild.hasNext()){
			//System.out.println(tree.getFile().getFilePointer());
			tree.getFile().writeBytes(iterChild.next().toString()+tree.getSeparatorAttribute());
		}
		//System.out.println(tree.getFile().getFilePointer());
		tree.getFile().writeBytes(""+tree.getSeparatorField());
			//INSERTO CONTADOR DE REGISTROS NECESARIO PARA EL GAMMA CODING
		tree.getFile().writeBytes(Integer.toString(getRegister_count())+tree.getSeparatorField());
			//INSERTO CONTADOR DE REGISTROS NECESARIO PARA EL GAMMA CODING
		
		while(iterReg.hasNext()){
			//System.out.println(tree.getFile().getFilePointer());
			tree.getFile().writeBytes(iterReg.next().write()+tree.getSeparatorAttribute());
		}
		//System.out.println(tree.getFile().getFilePointer());
		tree.getFile().writeBytes(""+tree.getSeparatorField());
		
		if(level==1){ //estoy escribiendo una hoja
			tree.getFile().writeBytes(Integer.toString(((Leaf) node).getPreviousLeaf())+tree.getSeparatorField());
			tree.getFile().writeBytes(Integer.toString(((Leaf) node).getNextLeaf())+tree.getSeparatorField());
		}
		while(tree.getFile().getFilePointer()%node.getNode_size()!=0){//node.getPosition()*node.getNode_size()-100
			tree.getFile().writeBytes("-"); //rellena con espacios
		}
		
	}
	
	/**
	 * returns the position of the largest less  than ID.
	 * Always go right of the return position
	 * 
	 * @param ID
	 * @return
	 */
	protected int getImmediateLess(RegisterID ID){
		ListIterator<Register> iterador=registerList.listIterator();
		RegisterID tempID;
		int position=-1;
		while(iterador.hasNext()){
			tempID=iterador.next().getID();
			position++;
			if(tempID.compareTo(ID)>0) return position-1; 
			//si encuentro uno tal que es mayor, entonces devuelvo el anterior que va a ser el ultimo menor
			// TODO revisar que esto sea correcto
		}
		return position; //todo otro caso, devuelvo la posicion (quiere decir que me pase)
	}
	
	/**
	 * FREE SPACE return method. Returns free space comparing print size of the node against the node available size.
	 * IMPORTANT! This result is calculated dynamically, it is never stored.
	 * 
	 * @return the free_space
	 */
	public int getFree_space() { //OJO, POR AHORA TOMO EN CUENTA UNICAMENTE EL TAMANIO DE LOS REGISTROS
		ListIterator<Register> iterador=registerList.listIterator();
		ListIterator<Integer> iterador2=childrenList.listIterator();
		
		free_space=node_size;
		while(iterador.hasNext()&&!registerList.isEmpty()){ //El isEmpty es redundante
			free_space-=(iterador.next().getSize()+1); //el 1 corresponde al separador de registros
		}
		free_space--; //corresponde al tamanio del separador de campo
		while(iterador2.hasNext()&&!childrenList.isEmpty()){ //El isEmpty es redundante
			free_space-=(iterador2.next().toString().length()+1); //el 1 corresponde al separador de registros
		}
		free_space--; //corresponde al tamanio del separador de campo
		
		free_space-=Integer.toString(getRegister_count()).length();//corresponde al contador de registros
		free_space--; //corresponde al tamanio del separador de campo
		
		
		if(level==0){
			free_space-=Integer.toString(((Leaf)this).getNextLeaf()).length();
			free_space--;//corresponde al tamanio del separador de campo
			free_space-=Integer.toString(((Leaf)this).getPreviousLeaf()).length();
			free_space--;//corresponde al tamanio del separador de campo
		}
		
		return free_space;
	}
	/**
	 * @param free_space the free_space to set
	 */
	public void setFree_space(int free_space) {
		this.free_space = free_space;
	}
	
	/**
	 * @return the node_size
	 */
	public int getNode_size() {
		return node_size;
	}
	/**
	 * @param node_size the node_size to set
	 */
	public void setNode_size(int node_size) {
		this.node_size = node_size;
	}
	
	/**
	 * @return the level
	 */
	public int getLevel() {
		return level;
	}
	/**
	 * @param level the level to set
	 */
	public void setLevel(int level) {
		this.level = level;
	}
	/**
	 * @return the position
	 */
	public int getPosition() {
		return position;
	}
	/**
	 * @param position the position to set
	 */
	public void setPosition(int position) {
		this.position = position;
	}
	/**
	 * @return the register_count
	 */
	public int getRegister_count() {
		return registerList.size();
	}
	/**
	 * @param register_count the register_count to set
	 */
	public void setRegister_count(int register_count) {
		this.register_count = register_count;
	}

	protected void insert(Register reg) throws NodeOverflowException, IOException, DuplicateRegisterException {
		//hago get ImmediateLess, del valor resuelto tengo que ir para el de la derecha...puede ser -1 en cuyo caso tengo que ir a 0
		int tempPosition = getImmediateLess(reg.getID())+1;
		//System.out.println(tempPosition);
		Node node = new Leaf(tree); //despues se elimina de todos modos
		try {
			//System.out.println("baja un nivel");
			//System.out.println(childrenList.getFirst());
			//System.out.println(tempPosition);
			//System.out.println(childrenList.isEmpty());
			node=loadChild(childrenList.get(tempPosition));
			node.insert(reg);
		} catch (NodeOverflowException e) {
				System.out.println("Cae al overflow de insercion");
			NodeOverflowExceptionHandler(node,tempPosition);
			Node superNode=new Node(level+1,tree);
			superNode.writeChild(this);
			if(getFree_space()<0){
				//INSERCION CON OVERFLOWWW
					throw new NodeOverflowException();
			}	
		}
		/*catch (DuplicateRegisterException e){
				System.out.println("en la insercion detecto el duplicado");
			modify(reg);
		}
		*/
		writeChild(node);
	}

	public void modify(Register reg) throws IOException, NodeOverflowException {
//		hago get ImmediateLess, del valor resuelto tengo que ir para el de la derecha...puede ser -1 en cuyo caso tengo que ir a 0
		int tempPosition = getImmediateLess(reg.getID())+1;
		Node node=new Leaf(tree); //despues no sirve
		try {
			node=loadChild(childrenList.get(tempPosition));
			node.modify(reg);
		} catch (NodeOverflowException e) {
			System.out.println("Cae al overflow de modificacion");
			
			NodeOverflowExceptionHandler(node,tempPosition);
			Node superNode=new Node(level+1,tree);
			superNode.writeChild(this);
			if(getFree_space()<0){
				//INSERCION CON OVERFLOWWW
					throw new NodeOverflowException();
			}
		}
		writeChild(node);
	}

	Register removeFirstReg() {
		return registerList.removeFirst();
	}

	Register getFirstReg() {
		return registerList.getFirst();
	}

	void setFirstReg(Register firstReg) {
		registerList.addFirst(firstReg);
	}

	Register removeLastReg() {
		return registerList.removeLast();
	}

	void setFirstChild(int firstChild) {
		childrenList.addFirst(firstChild);
	}

	int removeLastChild() {
		if (childrenList.isEmpty()) return 0; //caso de lista vacia, si no tira exception
		return childrenList.removeLast();
	}

	public Register search(RegisterID ID,RegisterIterator iterator) throws IOException {
		return loadChild(getImmediateLess(ID)+1).search(ID,iterator);
	}

	public RegisterIterator firstRegIterator() throws IOException {
		return loadChild(childrenList.get(0)).firstRegIterator();
	}
	
	public Node loadChild(int position) throws IOException{
		Node outputNode;
		if(level==1) outputNode=new Leaf(tree);
		else outputNode=new Node(level-1,tree);
		
		outputNode.position=position;
		tree.getFile().seek(position*node_size);
		//ListIterator<Register> iterReg=registerList.listIterator();
		//ListIterator<Integer> iterChild=childrenList.listIterator();
		String auxString;
		String[] subStrings;
		String temp;
		Register auxReg;
			//System.out.println(position);
		auxString=tree.getFile().readField();
		temp=""+tree.getSeparatorAttribute();
			//System.out.println(auxString);
		subStrings=auxString.split(temp);
			//System.out.println(level);
			//System.out.println(subStrings.length);
			//System.out.println(subStrings[0]);
		if(level!=1){
			for(int i=0; i<subStrings.length; i++){
				outputNode.childrenList.addLast(Integer.valueOf(subStrings[i]));
			}
		}
			//System.out.println(outputNode.childrenList.getFirst());
		
		//children ready
		auxString=tree.getFile().readField();//CORRESPONDIENTE AL NUMERO DE REGISTROS
											//VA A SER USADO CON EL GAMMA CODING
		
		auxString=tree.getFile().readField();
		temp=""+tree.getSeparatorAttribute();
		subStrings=auxString.split(temp);
		//System.out.println("cantidad de registros a leer = "+subStrings.length);
		if(subStrings[0].compareTo("")!=0){ //implica que no es nodo interno ni hoja vacia
			//System.out.println("registro a leer"+subStrings[0]);
			for(int i=0; i<subStrings.length; i++){
				auxReg=new Register();
				auxReg.load(subStrings[i]);
				outputNode.registerList.addLast(auxReg);
			}
		}	
		
		if(level==1){
			auxString=tree.getFile().readField();
				//System.out.println("Posicion de la hoja a cargar = " + position);
			//System.out.println("Posicion del puntero" + tree.getFile().getFilePointer());
				//System.out.println("Previous a la hoja a cargar = " + auxString);
			((Leaf) outputNode).setPreviousLeaf(Integer.valueOf(auxString));
			auxString=tree.getFile().readField();
			((Leaf) outputNode).setNextLeaf(Integer.valueOf(auxString));
		}
		//System.out.println("sale");
		return outputNode;
	}

	public BPlusTree getTree(){
		return tree;
	}
	 /**
	  * handles overflow and write
	  * WATCH OUT, tempPosition means the position in the index
	  * @param tempPosition
	  * @throws IOException
	  */
	private void NodeOverflowExceptionHandler(Node node, int tempPosition) throws IOException{
		Register tempReg;
		//		TODO insercion con overflow
		
		/*if(tempPosition>0){ //puedo ver el de la izquierda para balancear
			if(childrenList.get(tempPosition-1).getFree_space()+childrenList.get(tempPosition).getFree_space()>=getNode_size()*0.1){
				//En principio es posible balancear, si está muy justo puede ser que no encuentre un registro que acomode justito por lo que pido un 10% libre
				//asumo que asi se va a poder, quizas se rompe todo
				//TODO hacer el balanceo y decir que ya esta todo listo 
			}
		}
		if(tempPosition<getRegister_count()-2){ //puedo ver el de la derecha para balancear
			if(childrenList.get(tempPosition+1).getFree_space()+childrenList.get(tempPosition).getFree_space()>=getNode_size()*0.1){
				//En principio es posible balancear, si está muy justo puede ser que no encuentre un registro que acomode justito por lo que pido un 10% libre
				//asumo que asi se va a poder, quizas se rompe todo
				//TODO hacer el balanceo y decir que ya esta todo listo 
			}
		}*/
		
		//SI LLEGA HASTA ACA NECESITO UN SPLITTTT
		//System.out.println("Overflow del nodo " + node.getPosition() + " hijo en la posicion " + tempPosition);
		Node tempNode;
		if (getLevel()==1) tempNode=new Leaf(tree);
		else tempNode=new Node(getLevel()-1,tree);
		Node superLeaf=new Node(1,tree); //used to write the leaves
		
		
		while(tempNode.getFree_space()>tempNode.getNode_size()/2){
			//System.out.println("SPLIT! PASANDO REGISTRO = " + node.registerList.getLast().getID().getID());
			
			if(!node.registerList.isEmpty()) tempNode.setFirstReg(node.removeLastReg());
			if(!node.childrenList.isEmpty()) tempNode.setFirstChild(node.removeLastChild());
		}
		
		
		if (getLevel()==1){ // si lo que se partio es una hoja copia el Id del primer registro
			//TODO Agregarle un nodo artificial como first children pero no creo que sea necesario, si hay null no pasa nada
			tempReg= new Register(tempNode.getFirstReg().getID());
			//mantenimiento del encadenamiento de las hojas
			Leaf tempPrevLeaf, tempLeaf, tempNextLeaf;
			
			tempLeaf=(Leaf)tempNode;
			tempPrevLeaf=(Leaf)node;
			if(tempPrevLeaf.getNextLeaf()!=node.getPosition()){
				//System.out.println("Es aca que pincha?");
				tempNextLeaf=(Leaf)superLeaf.loadChild(tempPrevLeaf.getNextLeaf());
			}
			else 
				tempNextLeaf=(Leaf) node;
			
			tempLeaf.setPreviousLeaf(tempPrevLeaf.getPosition());
				//System.out.println(tempPrevLeaf.getPosition());
			tempLeaf.setNextLeaf(tempNextLeaf.getPosition());
				//System.out.println(tempNextLeaf.getPosition());
			superLeaf.writeChild(tempLeaf);
			tempPrevLeaf.setNextLeaf(tempLeaf.getPosition());
				//System.out.println(tempLeaf.getPosition());
			superLeaf.writeChild(tempPrevLeaf);
			tempNextLeaf.setPreviousLeaf(tempLeaf.getPosition());
				//System.out.println(tempLeaf.getPosition());
			superLeaf.writeChild(tempNextLeaf);
			//termina mantenimiento del encadenamiento de las hojas
			
		}
		else {		// si lo que se partio es un nodo interno promueve el primer registro
			tempReg=tempNode.removeFirstReg();
		}
		registerList.add(tempPosition, tempReg);	//ojo, cambiados por +1 y +2
		childrenList.add(tempPosition+1, tempNode.getPosition()); //estos +2 y +3 parecen magicos pero pensando que se inserta a la derecha y que tempPosition me da el Inmediato menor, tiene que andar
		
		superLeaf.writeChild(node);
		superLeaf.writeChild(tempNode);
		//aca van los writesssssssssssssssssssssssssss
		
	}
	
}
